﻿
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.IO;

using Swaf;
using Swaf.BizObj;
using Swaf.BizRule;
using Swaf.Container;
using Swaf.Security;

namespace Articles
{
	public class ArticleContentFilteringBizRules : Syrinx.BaseBizRules
	{
		protected static Regex s_htmlBlock = new Regex(@"</?([a-zA-Z_0-9:]+)(?:(?:\s+(\w+)(?:\s*=\s*(?:""(.*?)""|'(.*?)'|[^'"">\s]+))?)+\s*|\s*)/?>");

		public void filterArticleContentUpdate(IApplication app, IRuleContext context,
			IApplication app2, IRuleContext context2, IBizObj article)
		{
			if (article != null)
			{
				IBizObjList userGroups = context.RuleGroup.execute(context, "getUserUserGroups", CurrentUser.get("id")).CallResults as IBizObjList;
				if (userGroups != null && userGroups.numElements > 0)
				{
					string articleType = "Article";
					if ("Forum".CompareTo(article.get("treeName")) == 0)
						articleType = "Forum";
					//TODO: Need to find a good and fast way to know if a given article is actually a blog type.
					//Blog articles are still just Article bizobjtree types right now.  Maybe they need to be in their
					FlexiMap m = new FlexiMap();
					m.put("userGroupId", userGroups[0].get("id"));
					m.put("articleType", articleType);
					IBizObj filter = app.appData.get("DA.ArticleContentFiltering.GetArticleContentFilter", m) as IBizObj;

					if (filter != null)
					{
						if (article.isDirty("baseBody"))
							article.put("baseBody", filterContent(app, (string)article.get("baseBody"), filter));
						if (article.isDirty("baseSummary"))
							article.put("baseSummary", filterContent(app, (string)article.get("baseSummary"), filter));
						if (article.isDirty("baseTitle"))
							article.put("baseTitle", filterContent(app, (string)article.get("baseTitle"), filter));
						if (article.isDirty("baseCaption"))
							article.put("baseCaption", filterContent(app, (string)article.get("baseCaption"), filter));

						if (article.isDirty("bodyTranslation"))
							article.put("bodyTranslation", filterContent(app, (string)article.get("bodyTranslation"), filter));
						if (article.isDirty("summaryTranslation"))
							article.put("summaryTranslation", filterContent(app, (string)article.get("summaryTranslation"), filter));
						if (article.isDirty("titleTranslation"))
							article.put("titleTranslation", filterContent(app, (string)article.get("titleTranslation"), filter));
						if (article.isDirty("captionTranslation"))
							article.put("captionTranslation", filterContent(app, (string)article.get("captionTranslation"), filter));
					}
				}
			}
		}

		public string filterAllHtml(IApplication app, IRuleContext context, string content)
		{
			IBizObj filter = app.appData.get("StripAllHtmlFilter") as IBizObj;
			if (filter != null)
				return filterContent(app, content, filter);
			return content;
		}


		public static string filterContent(IApplication app, string content, IBizObj filter)
		{
			if (filter != null && !Cmn.IsEmpty(content))
			{
				bool killUpdateIfIllegalContent = !(bool)filter.get("stripDisallowedAndSave");
				bool onlySpecificAllowed = (bool)filter.get("allowNothingButSpecified");
				bool textOnly = (bool)filter.get("textOnly");

				StringBuilder buff = new StringBuilder(content.Length);
				MatchCollection mc = s_htmlBlock.Matches(content);
				bool includeContent = true;
				int lastPos = 0;
				string matchType = null, startMatchType = null, endMatchType = null;
				int matchTypeDepth = 0;
				int potentialContentStart = -1;
				bool inStrippedTable = false;
				bool inStrippedAnchor = false;
				string strippedElement = null;
				foreach (Match m in mc)
				{
					if (string.Compare(m.Groups[1].Value, "br", true) == 0 && textOnly)
					{
						buff.Append(processWordReplacement(content.Substring(lastPos, m.Index - lastPos), filter));
						buff.Append(" ");
					}
					else
					{

						if (!includeContent)
						{
							if (m.Groups[0].Value.StartsWith(startMatchType, StringComparison.CurrentCultureIgnoreCase))
								matchTypeDepth++;
							else if (m.Groups[0].Value.StartsWith(endMatchType, StringComparison.CurrentCultureIgnoreCase))
							{
								if (--matchTypeDepth == 0)
								{
									includeContent = true;

									if (potentialContentStart != -1)
									{
										string potentialContent = content.Substring(potentialContentStart, m.Index + m.Length - potentialContentStart);
										if (matchType == "object")
										{
											int startIndex = potentialContent.IndexOf("<embed", StringComparison.CurrentCultureIgnoreCase);
											int endIndex = potentialContent.IndexOf("</embed>", StringComparison.CurrentCultureIgnoreCase);
											if (startIndex != -1 && endIndex != -1)
											{
												string embedStr = potentialContent.Substring(startIndex, endIndex + 8 - startIndex);
												buff.Append(filterContent(app, embedStr, filter));
											}
										}
										potentialContentStart = -1;
									}
								}
							}
						}
						else
						{
							buff.Append(processWordReplacement(content.Substring(lastPos, m.Index - lastPos), filter));
							bool writeElement = true;
							if (includeContent)
							{
								if (Cmn.IsFalse(filter.get("allowClientScripts")))
								{
									if (m.Groups[0].Value.StartsWith("<script", StringComparison.CurrentCultureIgnoreCase))
									{
										includeContent = false;
										setupMatchTypes("script", out matchType, out startMatchType, out endMatchType, out matchTypeDepth);
									}
									//TODO: Search for event attributes with javascript event handlers.
								}
								if (Cmn.IsFalse(filter.get("allowIFrames")))
								{
									if (m.Groups[0].Value.StartsWith("<iframe", StringComparison.CurrentCultureIgnoreCase))
									{
										includeContent = false;
										setupMatchTypes("iframe", out matchType, out startMatchType, out endMatchType, out matchTypeDepth);
									}
								}
								if (Cmn.IsFalse(filter.get("allowExternalImages")))
								{
									if (m.Groups[0].Value.StartsWith("<img", StringComparison.CurrentCultureIgnoreCase))
									{
										for (int pos = 0; pos < m.Groups[2].Captures.Count; pos++)
											if (string.Compare(m.Groups[2].Captures[pos].Value, "src", true) == 0 &&
												m.Groups[3].Captures[pos].Value.IndexOf(':') != -1)
											{
												includeContent = false;
												setupMatchTypes("img", out matchType, out startMatchType, out endMatchType, out matchTypeDepth);
												break;
											}
									}
								}
								if (Cmn.IsFalse(filter.get("allowTables")))
								{
									if (m.Groups[0].Value.StartsWith("<table", StringComparison.CurrentCultureIgnoreCase))
									{
										writeElement = false;
										inStrippedTable = true;
									}
									else if (m.Groups[0].Value.StartsWith("</table", StringComparison.CurrentCultureIgnoreCase))
									{
										buff.Append(textOnly ? " " : "<br/>");
										writeElement = false;
										inStrippedTable = false;
									}
									else if (inStrippedTable &&
											(string.Compare(m.Groups[1].Value, "tr", true) == 0 ||
											 string.Compare(m.Groups[1].Value, "td", true) == 0 ||
											 string.Compare(m.Groups[1].Value, "th", true) == 0 ||
											 string.Compare(m.Groups[1].Value, "tbody", true) == 0 ||
											 string.Compare(m.Groups[1].Value, "col", true) == 0 ||
											 string.Compare(m.Groups[1].Value, "colgroup", true) == 0))
									{
										if (m.Groups[0].Value.StartsWith("<td", StringComparison.CurrentCultureIgnoreCase))
											buff.Append(textOnly ? " " : "<br/>");
										writeElement = false;
									}

								}
								if (Cmn.IsFalse(filter.get("allowPBUIH")))
								{
									if (string.Compare(m.Groups[1].Value, "p", true) == 0 ||
										string.Compare(m.Groups[1].Value, "b", true) == 0 ||
										string.Compare(m.Groups[1].Value, "u", true) == 0 ||
										string.Compare(m.Groups[1].Value, "i", true) == 0 ||
										string.Compare(m.Groups[1].Value, "div", true) == 0 ||
										string.Compare(m.Groups[1].Value, "span", true) == 0 ||
										(m.Groups[1].Value.StartsWith("h", StringComparison.CurrentCultureIgnoreCase) && m.Groups[1].Value.Length == 2))
										writeElement = false;

									if ((string.Compare(m.Groups[1].Value, "p", true) == 0 ||
										string.Compare(m.Groups[1].Value, "div", true) == 0 ||
										(m.Groups[1].Value.StartsWith("h", StringComparison.CurrentCultureIgnoreCase) && m.Groups[1].Value.Length == 2))
										&& m.Groups[0].Value[1] == '/')
										buff.Append(textOnly ? " " : "<br/>");


								}
								if (Cmn.IsFalse(filter.get("allowLists")))
								{
									if (string.Compare(m.Groups[1].Value, "ul", true) == 0 ||
										string.Compare(m.Groups[1].Value, "ol", true) == 0 ||
										string.Compare(m.Groups[1].Value, "li", true) == 0)
										writeElement = false;

									if (string.Compare(m.Groups[1].Value, "li", true) == 0 && m.Groups[0].Value[1] == '/')
										buff.Append(textOnly ? " " : "<br/>");
								}
								if (string.Compare(m.Groups[1].Value, "a", true) == 0)
								{
									if (textOnly || Cmn.IsFalse(filter.get("allowExternalAnchors")))
										if (m.Groups[0].Value.StartsWith("</") && inStrippedAnchor)
										{
											writeElement = false;
											inStrippedAnchor = false;
										}
										else
											for (int pos = 0; pos < m.Groups[2].Captures.Count; pos++)
												if (textOnly || (string.Compare(m.Groups[2].Captures[pos].Value, "href", true) == 0 &&
													m.Groups[3].Captures[pos].Value.IndexOf(':') != -1))
												{
													writeElement = false;
													inStrippedAnchor = true;
													break;
												}
								}
								else if (m.Groups[0].Value.StartsWith("<object", StringComparison.CurrentCultureIgnoreCase))
								{
									bool isFlashObject = false;
									bool hasClassId = false;
									for (int pos = 0; pos < m.Groups[2].Captures.Count; pos++)
									{
										if (string.Compare(m.Groups[2].Captures[pos].Value, "classid", true) == 0)
										{
											hasClassId = true;
											isFlashObject =
												(string.Compare(m.Groups[3].Captures[pos].Value,
														"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", true) == 0 ||
												 string.Compare(m.Groups[3].Captures[pos].Value,
														"D27CDB6E-AE6D-11cf-96B8-444553540000", true) == 0);
											break;

										}
										else if (string.Compare(m.Groups[2].Captures[pos].Value, "type", true) == 0 &&
												 string.Compare(m.Groups[3].Captures[pos].Value, "application/x-shockwave-flash", true) == 0)
											isFlashObject = true;
									}

									bool allowObject = ((hasClassId && !isFlashObject && (bool)filter.get("allowOtherObjects")) || isFlashObject && (bool)filter.get("allowFlashObjects"));
									if (!allowObject)
									{
										includeContent = false;
										setupMatchTypes("object", out matchType, out startMatchType, out endMatchType, out matchTypeDepth);
										if ((!hasClassId && (Cmn.IsTrue(filter.get("allowFlashObjects")) || Cmn.IsTrue(filter.get("allowYouTubeFlashObjects")))) ||
											(hasClassId && isFlashObject && Cmn.IsTrue(filter.get("allowYouTubeFlashObjects"))))
											potentialContentStart = m.Index;
									}
								}
								else if (m.Groups[0].Value.StartsWith("<embed", StringComparison.CurrentCultureIgnoreCase))
								{
									bool isFlashObject = false;
									bool isYouTube = false;
									for (int pos = 0; pos < m.Groups[2].Captures.Count; pos++)
									{
										if (string.Compare(m.Groups[2].Captures[pos].Value, "type", true) == 0 &&
											string.Compare(m.Groups[3].Captures[pos].Value, "application/x-shockwave-flash", true) == 0)
											isFlashObject = true;
										else if (string.Compare(m.Groups[2].Captures[pos].Value, "src", true) == 0 &&
											m.Groups[3].Captures[pos].Value.StartsWith("http://www.youtube.com/", StringComparison.CurrentCultureIgnoreCase))
											isYouTube = true;
									}
									if (!((isFlashObject && isYouTube && Cmn.IsTrue(filter.get("allowYouTubeFlashObjects"))) ||
										(isFlashObject && !isYouTube && Cmn.IsTrue(filter.get("allowFlashObjects"))) ||
										(!isFlashObject && Cmn.IsTrue(filter.get("allowOtherObjects")))))
									{
										includeContent = false;
										setupMatchTypes("embed", out matchType, out startMatchType, out endMatchType, out matchTypeDepth);
									}
								}
								else if (onlySpecificAllowed)
									writeElement = false;

								if (includeContent && writeElement)
									buff.Append(m.Groups[0].Value);
							}
						}
					}
					lastPos = m.Index + m.Length;
					if (killUpdateIfIllegalContent && !includeContent)
						throw new ArticlesException(string.Format("Illegal content in article update: {0}", m.Groups[0].Value));
				}
				buff.Append(content.Substring(lastPos));
				content = buff.ToString();
			}
			if ((bool)filter.get("shrinkWhitespace"))
			{
				Regex ws = new Regex("\\s+");
				content =ws.Replace(content, " ");
			}
			return content;
		}

		public static string processWordReplacement(string source, IBizObj filter)
		{
			string wordReplacementFile = (string)filter.get("wordReplacementFile");
			if (!Cmn.IsEmpty(wordReplacementFile))
			{
			}
			return source;
		}

		public static void setupMatchTypes(string type, out string matchType, out string startMatchType, out string endMatchType, out int depth)
		{
			matchType = type;
			startMatchType = "<" + type;
			endMatchType = "</" + type;
			depth = 1;
		}

		public IBizObjList getArticleContentFilters(IApplication app, IRuleContext context, Swaf.BGuid.Guid userGroupId)
		{
			FlexiMap m = new FlexiMap();
			m.put("userGroupId", userGroupId);
			IBizObjList filters = app.appData.get("DA.ArticleContentFiltering.GetArticleContentFilters", m) as IBizObjList;

			return filters;
		}

		public IBizObj getArticleContentFilter(IApplication app, IRuleContext context, Swaf.BGuid.Guid id)
		{
			FlexiMap m = new FlexiMap();
			m.put("id", id);
			IBizObj filter = app.appData.get("DA.ArticleContentFiltering.GetArticleContentFilter", m) as IBizObj;
			return filter;
		}

		public void deleteArticleContentFilter(IApplication app, IRuleContext context, Swaf.BGuid.Guid id)
		{
			FlexiMap m = new FlexiMap();
			m.put("id", id);
			app.appData.put("DA.ArticleContentFiltering.DeleteArticleContentFilter", m);
		}

		public void updateArticleContentFilter(IApplication app, IRuleContext context, IBizObj filter)
		{
			if(filter.isNew)
				app.appData.put("DA.ArticleContentFiltering.AddArticleContentFilter", filter);
			else
				app.appData.put("DA.ArticleContentFiltering.UpdateArticleContentFilter", filter);
		}

		public IBizObjList getArticleFilterArticleTypes(IApplication app, IRuleContext context)
		{
			return app.appData.get("AllowableArticleTypesForContentFiltering") as IBizObjList;
		}
	}
}
